﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace AutoMapper
{

    public static class AutoMapperExtensions
    {

        public static MemberInfo GetSourceMemberInfo(this PropertyMap map)
        {
            return (map?.GetSourceValueResolvers().FirstOrDefault() as IMemberGetter)?.MemberInfo;
        }

        public static TDestination MapSingle<TSource, TDestination, TProperty>(this TSource source, TDestination destination, Expression<Func<TSource, TProperty>> propertyLambda)
        {
            var propertyName = source.GetPropertyInfo(propertyLambda)?.Name;
            return MapSingle(source, destination, propertyName);
        }

        public static TDestination MapSingle<TSource, TDestination>(this TSource source, TDestination destination, string propertyName)
        {
            string destinationPropertyName;
            return MapSingle(source, destination, propertyName, out destinationPropertyName);
        }

        public static TDestination MapSingle<TSource, TDestination>(this TSource source, TDestination destination, string propertyName, out string destinationPropertyName)
        {
            var mappings = Mapper.FindTypeMapFor<TSource, TDestination>().GetPropertyMaps();
            var mapping = mappings.FirstOrDefault(x => x.SourceMember?.Name == propertyName);

            var newValue = source.GetType().GetRuntimeProperty(mapping.SourceMember.Name).GetValue(source);
            var targetValue = Mapper.Map(newValue, (mapping.SourceMember as PropertyInfo)?.PropertyType, mapping.DestinationPropertyType);

            destinationPropertyName = mapping?.DestinationProperty?.Name;
            (mapping?.DestinationProperty?.MemberInfo as PropertyInfo)?.SetValue(destination, targetValue);

            return destination;
        }

        public static void AddEnumConverter<TEnum>(this Profile profile) where TEnum : struct
        {
            var converter = new EnumValueConverter<TEnum>();
            AddEnumConverter<TEnum, string>(profile, converter);
        }

        public static void AddEnumConverter<TEnum, TDestination, TConverter>(this Profile profile) where TEnum : struct where TConverter : IValueConverter
        {
            AddEnumConverter<TEnum, TDestination>(profile, Activator.CreateInstance<TConverter>());
        }

        public static void AddEnumConverter<TEnum, TDestination>(this Profile profile, IValueConverter converter) where TEnum : struct
        {
            profile.CreateMap<TEnum, TDestination>()
                .ConvertUsing((@enum) =>
                    (TDestination)converter.Convert(@enum, typeof(string), null, null));

            profile.CreateMap<TDestination, TEnum>()
                .ConvertUsing((str) =>
                    (TEnum)converter.ConvertBack(str, typeof(TEnum), default(TEnum), null));
        }

    }

}